﻿using Dapper;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
namespace Gaea
{
    internal static partial class GaeaSuperPower
    {
        public static T FindById<T>(this IDbConnection dbConnection, GaeaBasicPower gaeaBasicPower, dynamic id, IDbTransaction dbTransaction = null, int? commandTimeout = null) where T : class
        {
            var typeOfGaeaSon = typeof(T);
            if (!_findByIdCommandTexts.TryGetValue(typeOfGaeaSon.TypeHandle, out Tuple<string, string> commandTextInfo))
            {
                var gaeaName = BuildGaeaName(gaeaBasicPower, typeOfGaeaSon);
                var key = GetSingleKey<T>(nameof(FindByIdAsync));
                var sqlDapter = dbConnection.GetGaeaAdapter();
                var commandText = sqlDapter.FindByIdCommandText(out string paramName, gaeaName, key.Name);
                commandTextInfo = new Tuple<string, string>(commandText, paramName);
                _findByIdCommandTexts[typeOfGaeaSon.TypeHandle] = commandTextInfo;
            }

            var dynamicParameters = new DynamicParameters();
            dynamicParameters.Add(commandTextInfo.Item2, id);

            T gaeaSon;
            if (typeOfGaeaSon.IsInterface)
            {
                var queryResult = dbConnection.QuerySingleOrDefault(commandTextInfo.Item1, dynamicParameters) as IDictionary<string, object>;
                if (queryResult == null)
                {
                    return null;
                }

                gaeaSon = ProxyGenerator.GetInterfaceProxy<T>();

                foreach (var property in TypePropertiesCache(typeOfGaeaSon))
                {
                    var val = queryResult[property.Name];
                    if (val == null) continue;
                    if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        var genericType = Nullable.GetUnderlyingType(property.PropertyType);
                        if (genericType != null) property.SetValue(gaeaSon, Convert.ChangeType(val, genericType), null);
                    }
                    else
                    {
                        property.SetValue(gaeaSon, Convert.ChangeType(val, property.PropertyType), null);
                    }
                }
                ((IProxy)gaeaSon).IsDirty = false;
            }
            else
            {
                gaeaSon = dbConnection.QuerySingleOrDefault<T>(commandTextInfo.Item1, dynamicParameters, dbTransaction, commandTimeout: commandTimeout);
            }
            return gaeaSon;
        }

        public static IEnumerable<T> FindAll<T>(this IDbConnection dbConnection, GaeaBasicPower gaeaBasicPower, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
        {
            var typeOfGaeaSon = typeof(T);
            var gaeaAdapter = dbConnection.GetGaeaAdapter();
            var gaeaName = BuildGaeaName(gaeaBasicPower, typeOfGaeaSon);
            var commandText = gaeaAdapter.FindAllCommandText(gaeaName);
            return dbConnection.Query<T>(commandText, null, transaction, commandTimeout: commandTimeout);
        }

        public static int Insert<T>(this IDbConnection dbConnection, GaeaBasicPower gaeaBasicPower, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
        {
            var typeOfGaeaSon = typeof(T);
            var isArray = false;
            if (typeOfGaeaSon.IsArray)
            {
                isArray = true;
                typeOfGaeaSon = typeOfGaeaSon.GetElementType();
            }
            else if (typeOfGaeaSon.IsGenericType)
            {
                var implementsGenericIEnumerableOrIsGenericIEnumerable = typeOfGaeaSon.GenericTypeArguments.Any(lm => lm.IsGenericType && lm.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeOfGaeaSon.GetGenericTypeDefinition() == typeof(IEnumerable<>);

                if (implementsGenericIEnumerableOrIsGenericIEnumerable)
                {
                    isArray = true;
                    typeOfGaeaSon = typeOfGaeaSon.GetGenericArguments()[0];
                }
            }

            var gaeaAdapter = dbConnection.GetGaeaAdapter();
            if (!_insertCommandTexts.TryGetValue(typeOfGaeaSon.TypeHandle, out string commandText))
            {
                var gaeaName = BuildGaeaName(gaeaBasicPower, typeOfGaeaSon);
                var allProperties = TypePropertiesCache(typeOfGaeaSon);
                var keyProperties = KeyPropertiesCache(typeOfGaeaSon);

                var allPropertiesExceptKey = allProperties.Except(keyProperties).ToList();


                commandText = gaeaAdapter.InsertCommandText(gaeaName, allPropertiesExceptKey);
                _insertCommandTexts.TryAdd(typeOfGaeaSon.TypeHandle, commandText);
            }
            if (!isArray)
            {
                return gaeaAdapter.Insert(dbConnection, transaction, commandText, entityToInsert, commandTimeout);
            }
            else
            {
                return dbConnection.Execute(commandText, entityToInsert, transaction, commandTimeout);
            }

        }

        public static int Update<T>(this IDbConnection dbConnection, GaeaBasicPower gaeaBasicPower, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var typeOfGaeaSon = typeof(T);
            if (typeOfGaeaSon.IsArray)
            {
                typeOfGaeaSon = typeOfGaeaSon.GetElementType();
            }
            else if (typeOfGaeaSon.IsGenericType)
            {
                var implementsGenericIEnumerableOrIsGenericIEnumerable = typeOfGaeaSon.GenericTypeArguments.Any(lm => lm.IsGenericType && lm.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeOfGaeaSon.GetGenericTypeDefinition() == typeof(IEnumerable<>);

                if (implementsGenericIEnumerableOrIsGenericIEnumerable)
                {
                    typeOfGaeaSon = typeOfGaeaSon.GetGenericArguments()[0];
                }
            }

            var gaeaAdapter = dbConnection.GetGaeaAdapter();
            if (!_updateCommandTexts.TryGetValue(typeOfGaeaSon.TypeHandle, out string commandText))
            {
                var gaeaName = BuildGaeaName(gaeaBasicPower, typeOfGaeaSon);
                var allProperties = TypePropertiesCache(typeOfGaeaSon);
                var keyProperties = KeyPropertiesCache(typeOfGaeaSon);

                var allPropertiesExceptKey = allProperties.Except(keyProperties).ToList();

                commandText = gaeaAdapter.UpdateCommandText(gaeaName, allPropertiesExceptKey, keyProperties);
                _updateCommandTexts.TryAdd(typeOfGaeaSon.TypeHandle, commandText);
            }
            return dbConnection.Execute(commandText.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
        }

        public static int Delete<T>(this IDbConnection dbConnection, GaeaBasicPower gaeaBasicPower, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            if (entityToDelete == null)
            {
                throw new ArgumentException("Cannot delete null object", nameof(entityToDelete));
            }
            var typeOfGaeaSon = typeof(T);
            if (typeOfGaeaSon.IsArray)
            {
                typeOfGaeaSon = typeOfGaeaSon.GetElementType();
            }
            else if (typeOfGaeaSon.IsGenericType)
            {
                var implementsGenericIEnumerableOrIsGenericIEnumerable = typeOfGaeaSon.GenericTypeArguments.Any(lm => lm.IsGenericType && lm.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeOfGaeaSon.GetGenericTypeDefinition() == typeof(IEnumerable<>);

                if (implementsGenericIEnumerableOrIsGenericIEnumerable)
                {
                    typeOfGaeaSon = typeOfGaeaSon.GetGenericArguments()[0];
                }
            }
            var gaeaAdapter = dbConnection.GetGaeaAdapter();
            var gaeaName = BuildGaeaName(gaeaBasicPower, typeOfGaeaSon);
            var keyProperties = KeyPropertiesCache(typeOfGaeaSon);
            var commandText = gaeaAdapter.DeleteCommandText(gaeaName, keyProperties);
            return dbConnection.Execute(commandText, entityToDelete, transaction, commandTimeout);
        }

        public static int DeleteAll<T>(this IDbConnection dbConnection, GaeaBasicPower gaeaBasicPower, IDbTransaction transaction = null, int? commandTimeout = null, bool keyRestart = false)
        {
            var typeOfGaeaSon = typeof(T);
            var gaeaAdapter = dbConnection.GetGaeaAdapter();
            var gaeaName = BuildGaeaName(gaeaBasicPower, typeOfGaeaSon);
            var commandText = gaeaAdapter.DeleteAllCommandText(gaeaName, keyRestart);
            return dbConnection.Execute(commandText, null, transaction, commandTimeout);
        }
    }
}
